package uk.ac.lkl.common.allocator.ui;



import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.logging.Logger;

import javax.swing.*;

import uk.ac.lkl.common.allocator.AllocatorModel;
import uk.ac.lkl.common.allocator.event.*;
import uk.ac.lkl.common.ui.ViewCreator;
import uk.ac.lkl.common.util.ds.*;
import uk.ac.lkl.common.util.ds.event.*;
//import uk.ac.lkl.lpp.ui.Messages;

// this is ugly: we shouldn't depend on lpp classes, but no other quick fix
// UGLY HACK
import uk.ac.lkl.lpp.model.ldo.LearningEntity;
import uk.ac.lkl.lpp.ui.EntityNameButton;

/**
 * This class creats the Allocate panel.
 * 
 * @author Darren Pearce
 *
 * @param <O>
 */
public class SwitchingAllocatorView<O> extends AllocatorView<O> {

    /**
	 * 
	 */
	private static final long serialVersionUID = 2506205754929367025L;

	private static final Logger logger = Logger.getLogger("uk.ac.lkl.lpp");

	public static final String ALLOCATOR_PANEL_KEY = "AlocatorPanel";
    // map from object (such as topic) to row it is on
    private HashMap<O, Integer> rowMap = new HashMap<O, Integer>();

    private GridBagLayout layout = new GridBagLayout();

    private GridBagConstraints c = new GridBagConstraints();

    private ViewCreator<O> viewCreator;

    // map from object to its collection of cells.
    private HashMap<O, ArrayList<CellBlock>> cellMap = new HashMap<O, ArrayList<CellBlock>>();

    // maps each row to the object it shows
    private HashMap<Integer, O> objectMap = new HashMap<Integer, O>();

    private boolean useCheckBoxes = true;

    private JCheckBox useCheckBoxesCheckBox = new JCheckBox();

    private AbstractAllocatorController<O> controller;

    private HashMap<O, Component> viewMap = new HashMap<O, Component>();


    public SwitchingAllocatorView(AllocatorModel<O> allocator,ViewCreator<O> viewCreator,String title){
    	this(allocator,viewCreator);    	
    }
    
    public SwitchingAllocatorView(AllocatorModel<O> allocator,
            ViewCreator<O> viewCreator) {
        this(allocator, viewCreator, true);
    }


    public SwitchingAllocatorView(AllocatorModel<O> allocator,
            ViewCreator<O> viewCreator, boolean useCheckBoxes) {
        super(allocator);
       
        if(allocator==null){
        	logger.severe("null allocator");
        }
        JLabel j = new JLabel(allocator.getTitle());
        j.setLabelFor(this);
        j.setFont(new Font("Serif", Font.BOLD, 16));
        j.setForeground(Color.DARK_GRAY);
        
        this.add(j);
        
        // todo: put this in base class
        this.viewCreator = viewCreator;
        allocator.addAllocatorListener(new AllocatorListener<O>() {

            public void allocationChanged(AllocatorEvent<O> e) {
                processAllocationChanged(e);
            }

        });

        initialiseLayout(useCheckBoxes);
        processCollection();

        setController(new AllocatorController<O>());

        // hack
        allocator.getList().addListListener(new ListListener<O>() {

            public void elementAdded(ListEvent<O> e) {
                processElementAdded(e.getElement());
            }


            public void elementMoved(ListEvent<O> e) {
                processElementMoved(e.getElement());
            }


            public void elementRemoved(ListEvent<O> e) {
                processElementRemoved(e.getElement());
            }
        });

        initialseCellBlocks();
        //throw new NullPointerException("Just Checking");
    }


    public String getName() {
        return "Switching Allocator";
    }


    /**
     * Method should be in base-class. Need support for matching view to
     * controller, etc.
     * 
     * @param controller
     * 
     */
    public void setController(AbstractAllocatorController<O> controller) {
        if (this.controller == controller)
            return;

        if (this.controller != null)
            this.controller.uninstall(this);

        this.controller = controller;

        if (this.controller != null)
            this.controller.install(this);
    }


    private void processCollection() {
        AbstractNotifyingList<O> collection = allocator.getList();
        for (O object : collection) {
            initialiseRow(object);
        }
    }


    public O getObject(int row) {
        return objectMap.get(row);
    }


    // hack: should be a more robust approach to this
    public void setMobileCell(O object, int index, boolean mobile) {
        ArrayList<CellBlock> cellBlocks = cellMap.get(object);
        cellBlocks.get(index).setMobile(mobile);
    }


    public void clearMobileCells(O object) {
        ArrayList<CellBlock> cellBlocks = cellMap.get(object);
        for (CellBlock cellBlock : cellBlocks)
            cellBlock.setMobile(false);
    }


    public int getNumRows() {
        return objectMap.size();
    }


    public int getNumColumns() {
        return allocator.getNumColumns();
    }


    /**
     * @param useCheckBoxes
     */
    private void initialiseLayout(boolean useCheckBoxes) {
        setLayout(layout);

        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;

        if (useCheckBoxes) {
            c.gridx = 0;
            c.gridy = 0;
            
            //A quick way to get rid of this, 
            //To do: get rid of the code as well...
            //add(useCheckBoxesCheckBox, c); 
            
            
            useCheckBoxesCheckBox.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    SwitchingAllocatorView.this.useCheckBoxes = useCheckBoxesCheckBox
                            .isSelected();
                    updateCheckBoxes();
                }
            });
            useCheckBoxesCheckBox.setSelected(useCheckBoxes);
        }

        this.useCheckBoxes = useCheckBoxes;

        // starts at 1 so has space for entities down left hand side.
        for (int i = 1; i <= allocator.getNumColumns(); i++) {
            c.gridx = i;
            c.gridy = 0;
            
            JLabel label = new JLabel(" " + i);
//            JLabel label = new JLabel(allocator.getTimeUnit()+" " + i);
            label.setBorder(BorderFactory.createEtchedBorder());
            label.setBackground(Color.white);
            label.setOpaque(true);
            add(label, c);
        }

    }


    // note: calling this before on screen doesn't fire action event
    // since not visible
    public void setUseCheckBoxes(boolean useCheckBoxes) {
        useCheckBoxesCheckBox.setSelected(useCheckBoxes);
    }


    private void updateCheckBoxes() {
        for (ArrayList<CellBlock> blocks : cellMap.values()) {
            for (CellBlock block : blocks) {
                block.setCheckBoxVisible(useCheckBoxes);
            }
        }
    }


    // todo - make it apply retrospectively to previously-created
    // rows
    public void setViewCreator(ViewCreator<O> viewCreator) {
        this.viewCreator = viewCreator;
    }


    /**
     * Sync view with model.
     * 
     */
    private void initialseCellBlocks() {
        int numColumns = allocator.getNumColumns();
        Collection<O> collection = allocator.getList();

        for (O object : collection) {
            for (int i = 0; i < numColumns; i++) {
                updateCellBlock(object, i);
            }
        }
    }


    public void processAllocationChanged(AllocatorEvent<O> e) {
        O object = e.getObject();
        Integer row = rowMap.get(object);
        if (row == null)
            initialiseRow(object);
        int index = e.getIndex();
        updateCellBlock(object, index);
    }


    private void updateCellBlock(O object, int index) {
        ArrayList<CellBlock> cellBlocks = cellMap.get(object);
        int allocation = allocator.getAllocation(object, index);
        CellBlock cellBlock = cellBlocks.get(index);
        cellBlock.setCount(allocation);
    }


    // assumes at end of list
    private void processElementAdded(O object) {
        initialiseRow(object);
        validate();
    }


    private void processElementMoved(O object) {
        // code this. At moment, since rows are empty, no need to move
        // components around. Hack!
        validate();
    }


    private void processElementRemoved(O object) {
        ArrayList<CellBlock> cellBlocks = cellMap.get(object);
        Component view = viewMap.get(object);
        // removing TMs was doing a NullPointer here
        if (view != null) remove(view);
        for (CellBlock cellBlock : cellBlocks)
            remove(cellBlock);
        validate();
    }


    private void initialiseRow(O object) {
        int row = rowMap.size() + 1; // +1 so skips header row
        rowMap.put(object, row);
        objectMap.put(row, object);
        
        // initialise row title
        c.gridx = 0;
        c.gridy = row;
        
        if (object instanceof LearningEntity) {
        	EntityNameButton enb = new EntityNameButton((LearningEntity)object,20,20);
        	add(enb,c);
        } else {
        	add(new AllocateLabel(object),c);
        }
        
        
        // create new ArrayList for cells for this object
        ArrayList<CellBlock> cellBlocks = new ArrayList<CellBlock>();

        // initialse row cells
        for (int i = 1; i <= allocator.getNumColumns(); i++) {
            c.gridx = i;
            CellBlock cellBlock = new CellBlock(useCheckBoxes);
            add(cellBlock, c);
            cellBlocks.add(cellBlock);
            cellMap.put(object, cellBlocks);
        }
    }


    public Point getCell(int x, int y) {
    	
    	return layout.location(x, y);
    }

    // public void processAllocationDecreased(AllocatorEvent<O> e) {
    // O object = e.getObject();
    // int index = e.getIndex();
    // ArrayList<CellBlock> cellBlocks = cellMap.get(object);
    // CellBlock cellBlock = cellBlocks.get(index);
    // cellBlock.decrementCount();
    // }

}
